﻿using System;
using System.Collections.Generic;
using System.Linq;
using MbUnit.Framework;
using Microsoft.SharePoint;
using Sapphire.Linq.Tests.DataContext;
using TypeMock;
using TypeMock.ArrangeActAssert;

namespace Sapphire.Linq.Tests
{
    [Isolated]
    public class DataContextBaseTests
    {
        protected TasksDataContext DataContext;
        protected List<SPQuery> LastQueries;

        [SetUp]
        public void setup_last_query()
        {
            LastQueries = new List<SPQuery>();
        }

        [FixtureSetUp]
        public void setup_tests_context()
        {
            const string tasksListUrl = "Lists/Tasks";
            const string itemsListUrl = "Lists/Custom1";

            var fakeSite = Isolate.Fake.Instance<SPSite>(Members.ReturnRecursiveFakes);

            Isolate.Swap.NextInstance<SPSite>().With(fakeSite);

            var fakeweb = fakeSite.OpenWeb();

            Isolate.WhenCalled(() => fakeweb.ServerRelativeUrl).WillReturn("/");

            var fakeTasksList = SetUpList<Task>(fakeweb, tasksListUrl);
            var fakeItemsList = SetUpList<Item>(fakeweb, itemsListUrl);

            SetUpTasksList(fakeTasksList);
            SetUpItemsList(fakeItemsList);

            DataContext = new TasksDataContext(fakeweb);
        }

        private void SetUpItemsList(SPList fakeItemsList)
        {
            Isolate.WhenCalled(() => fakeItemsList.Items)
                .DoInstead(GetFakeItems);

            var query = new SPQuery();
            Isolate.WhenCalled(() => fakeItemsList.GetItems(query))
                .DoInstead(GetFakeItems);
        }

        private SPListItemCollection GetFakeItems(MethodCallContext arg)
        {
            LastQueries.Add(arg.Parameters[0] as SPQuery);
            return Isolate.Fake.Instance<SPListItemCollection>();
        }

        private void SetUpTasksList(SPList fakeTasksList)
        {
            Isolate.WhenCalled(() => fakeTasksList.Items)
                .DoInstead(GetFakeItems);

            var query = new SPQuery();
            Isolate.WhenCalled(() => fakeTasksList.GetItems(query))
                .DoInstead(GetFakeItems);
        }

        private IEnumerable<SPListItem> GetItems(SPList fakeItemsList)
        {
            for (int i = 1; i < 6; i++)
            {
                var spListItem = Isolate.Fake.Instance<SPListItem>();
                spListItem[SPBuiltInFieldId.ID] = i;
                spListItem[SPBuiltInFieldId.Title] = "Item " + i;

                Isolate.WhenCalled(() => spListItem.ParentList).WillReturn(fakeItemsList);
                yield return spListItem;
            }
        }

        private static IEnumerable<SPListItem> GetTaskItems(SPList fakeTasksList)
        {
            for (int i = 1; i < 11; i++)
            {
                var spListItem = Isolate.Fake.Instance<SPListItem>();
                spListItem[SPBuiltInFieldId.ID] = i;
                spListItem[SPBuiltInFieldId.Title] = "Task " + i;

                Isolate.WhenCalled(() => spListItem.ParentList).WillReturn(fakeTasksList);
                yield return spListItem;
            }
        }

        private SPList SetUpList<T>(SPWeb fakeweb, string url)
        {
            var fakeList = Isolate.Fake.Instance<SPList>();

            Isolate.WhenCalled(() => fakeList.Title).WillReturn(typeof(T).Name + "s");

            Isolate.WhenCalled(() => fakeweb.GetList(url)).WillReturn(fakeList);

            Isolate.WhenCalled(() => fakeList.RootFolder.Url).WillReturn(url);

            var listFields = GetListFields<T>(fakeList).ToList();

            Isolate.WhenCalled(() => fakeList.Fields)
                .WillReturnCollectionValuesOf(listFields);

            return fakeList;
        }


        private static IEnumerable<SPField> GetListFields<T>(SPList fakeList)
        {
            return GetProperties(typeof(T))
                .ToList()
                .Select(pair =>
                            {
                                var fakeField = Isolate.Fake.Instance<SPField>();

                                Isolate.WhenCalled(() => fakeField.InternalName)
                                    .WillReturn(pair.Key);

                                Isolate.WhenCalled(() => fakeField.Id)
                                    .WillReturn(pair.Value);

                                Isolate.WhenCalled(() => fakeList.Fields[pair.Value])
                                  .WithExactArguments()
                                  .WillReturn(fakeField);

                                Isolate.WhenCalled(() => fakeList.Fields.GetFieldByInternalName(pair.Key))
                                   .WithExactArguments()
                                   .WillReturn(fakeField);

                                Isolate.WhenCalled(() => fakeList.Fields.ContainsField(pair.Key))
                                   .WithExactArguments()
                                   .WillReturn(true);

                                return fakeField;
                            });
        }

        private static Dictionary<string, Guid> GetProperties(Type type)
        {
            if (type == typeof(Item))
            {
                return new Dictionary<string, Guid>
                           {
                               { "ID", SPBuiltInFieldId.ID },
                               { "Title", SPBuiltInFieldId.Title},
                               { "Created", SPBuiltInFieldId.Created },
                               { "Author",SPBuiltInFieldId.Author },
                               { "Task", Constants.ItemTaskField },
                               { "Tasks", Constants.ItemTasksField }
                           };
            }

            if (type == typeof(Task))
            {
                return new Dictionary<string, Guid>
                           {
                              { "UniqueId",SPBuiltInFieldId.UniqueId},
                              { "ID", SPBuiltInFieldId.ID  },
                              { "Title", SPBuiltInFieldId.Title },
                              { "LinkTitle", SPBuiltInFieldId.LinkTitle },
                              { "AssignedTo", SPBuiltInFieldId.AssignedTo},
                              { "StartDate", SPBuiltInFieldId.StartDate},
                              { "DueDate",  SPBuiltInFieldId.TaskDueDate },
                              { "Status", SPBuiltInFieldId.TaskStatus },
                              { "ContentTypeId",  SPBuiltInFieldId.ContentTypeId },
                              { "Author", SPBuiltInFieldId.Author }
                           };
            }

            return new Dictionary<string, Guid>();
        }
    }
}